#include <stdio.h>
#include <stdlib.h>
#include <assert.h>



/* ----------------------------------- */
/*     Definition du type pixel        */
/* ----------------------------------- */


struct sPixel
{
  unsigned char red;
  unsigned char green;
  unsigned char blue;
};
typedef struct sPixel pixel;




/* -------------------------------------------------------------- */
/*     Declarations des procedures que vous pouvez appeler        */
/* (deja ecrites, code disponible pour information apres le main) */
/* -------------------------------------------------------------- */


/* Precondition : nomFichier est le nom d'un fichier au format Bitmap, 
   24-bit par pixel (donc pas de palette), non compresse. 
   Postconditions : largeur et hauteur contiennent la largeur (nombre de colonnes) 
   et la hauteur (nombre de lignes) de l'image, en nombre de pixels. 
*/
void lireEntete(const char nomFichier[], unsigned int * largeur, unsigned int * hauteur);


/* Preconditions : tab est un tableau 1D de pixels, suffisamment grand pour contenir 
   tous les pixels de l'image. nomFichier est le nom d'un fichier au format Bitmap, 
   24-bit par pixel (donc pas de palette), non compresse. largeur et hauteur 
   sont les dimensions de l'image en nombre de pixels.
   Postcondition : Chaque case de tab correspond a un pixel de l'image et contient
   ainsi les valeurs RGB de ce pixel. Les premieres cases de tab contiennent la ligne 
   de pixels tout en bas de l'image. Les cases suivantes contiennent les pixels de la 
   ligne juste au-dessus (2e ligne en partant du bas), etc, jusqu'a la ligne du haut 
   de l'image.
*/
void remplirTableauPixelsDepuisFichier(const char nomFichier[], pixel tab[], \
                                       unsigned int largeur, unsigned int hauteur);




/* Preconditions : tab est un tableau contenant largeur*hauteur pixels.
   Postconditions : Un nouveau fichier est cree, nomme comme precise dans la chaine
   de caracteres nomFic. Il est au format Bitmap, 24 bits par pixel, non compresse.
   Si un fichier de ce nom existait deja, il est ecrase.
 */
void ecrireFichier(const char nomFic[], const pixel tab[], \
                   unsigned int largeur, unsigned int hauteur);



/* Precondition: 0 <= numbit <= 7, 0 correspond au bit de poids faible
   Resultat: on recupere le bit numero 'numbit' de 'nombre', c'est-a-dire
   0 ou 1 stocke dans un unsigned char. 
*/
unsigned char getIemeBit(unsigned char nombre, unsigned char numbit);




/* Precondition: 0 <= numbit <= 7, 0 correspond au bit de poids faible
   Postcondition: le bit numero 'numbit' de 'nombre' est mis a 0
*/
void setIemeBit0(unsigned char * nombre, unsigned char numbit);




/* Precondition: 0 <= numbit <= 7, 0 correspond au bit de poids faible
   Postcondition: le bit numero 'numbit' de 'nombre' est mis a 1
*/
void setIemeBit1(unsigned char * nombre, unsigned char numbit);




/* -------------------------------------------------------------- */
/*     Ecrivez ci-dessous les codes des procedures demandees      */
/* -------------------------------------------------------------- */

/* Preconditions : color est un pointeur sur entier existant.
   postcondition: color est un entier compris entre 0 et 255.
*/
void linearisationPixel(int *color)
{
  if ((*color) < 0)
    {
      (*color) = 0 ;
    }
  if ((*color) > 255)
    {
      (*color) = 255 ;
    }
}

/* Preconditions : imageF est un tableau 1D de pixels, suffisamment grand pour contenir 
   tous les pixels de l'image après modification. imageO est un tableau de pixel qui 
   contient l'image originale à modifier en mode donnée. 
   Largeur et hauteur contiennent la largeur (nombre de colonnes) et la hauteur (nombre de lignes)
   de l'image final (en mmode données), en nombre de pixels.le pareamètre P est un entier qui détermine l'intensité du
   traitement sépia 'en mode donnée). Les valeurs des champs de chacun des pixels ne doivent pas être négative ni excèder 255,
   celles qui ne remplient pas ces conditions sont ramené à 0 ou 255 selon les cas avant opération.

   Postconditions : Chaque case de imageF correspond à un pixel de l'image "imageO" après opération . Les valeurs RGB de chaque pixel
   ont été préalablement modifié pour ainsi, appliquer le filtre sépia sur l'ensemble des pixel de imageF.
*/

void traitementSepia(const pixel * imageO, pixel * imageF, const unsigned int largeur, const unsigned int hauteur, const int p)
{
  unsigned int ligne, colonne ;
  int m ;
  int r, g, b;
  /* On vérifie pour chacun des champs du pixel selectioné si ils verifient bien les
     preconditions et dans le cas contraire on les ramène à des valeur connu
  */
  for(ligne = 0; ligne < hauteur; ligne ++)
    {
      for (colonne = 0; colonne < largeur; colonne ++)
        {
	
	  /* On calcul la moyenne m des trois valeurs R,G,B du pixel sélectioné */
	  m = (imageO[(ligne*largeur)+colonne].red + imageO[(ligne*largeur)+colonne].green + imageO[(ligne*largeur)+colonne].blue)/3 ;
        
	  /* On calcule les nouvelles valeurs de chacun des champs du pixel pour aboutir au Sepia */
	  
	  r  = m-(2*p)  ;
	  linearisationPixel(&r) ; 
	  imageF[ligne*largeur + colonne].red = r;
	  
	  g  = m - p ;
	  linearisationPixel(&g) ;
	  imageF[ligne*largeur + colonne].green = g;
	  
	  b  = m + 3 * p ;
	  linearisationPixel(&b) ;
	  imageF[ligne*largeur + colonne].blue = b;
	}
    }
}

/* Préconditions :pixColor un pixel en  mode donnée. pixColorFinal, un pixel en mode donnée-résultat.
   posO et posD, des unsigned char en mode donnée qui indique la position du bit Originale à copier
   dans pixColorFinal à la position posD.
   
   Postconditions : le bit du pixel pixcolor (pour une couleur donnée) à la position posO, est copié dans
   le pixel pixColorFinal à la position posD.
 */
void changePixIemeBit(const unsigned char pixColor, unsigned char *pixColorFinal, const unsigned char posO, const unsigned char posD)
{
  if(getIemeBit(pixColor, posO) == 1)
    setIemeBit1(pixColorFinal, posD);
  else
    setIemeBit0(pixColorFinal, posD);

}

/* Preconditions : imageFinal est un tableau 1D de pixels, suffisamment grand pour contenir 
   tous les pixels de l'image en mode donnée-resultat. imageVue est un tableau de pixels de l'image visible,
   et imageCache est un tableau de pixels de l'image à cacher.
   Largeur et hauteur contiennent la largeur (nombre de colonnes) et la hauteur (nombre de lignes)
   de l'image, en nombre de pixels (mode donées).
 
   Postconditions : les pixels de imageFinal contiennent pour chaque valeurs (R,G,B) les 3 bits de poid fort de imageCache
   à la place des 3 bites de poid faible puis pour les 5 suivants, les 5 pixels de imageVue en partant du 4eme bit.
*/
void dissimulation (const pixel *imageVue, const pixel *imageCache, pixel * imageFinal, const unsigned int largeur, const unsigned int hauteur)
{
  unsigned char posO ; /* position d'origine du bit de poid fort de l'imageCache à placer dans les bites de poid faible de imageFinal */ 
  unsigned char posD ; /* position de destination du bit de poid faible de imageFinal */
  int ligne, colonne ; /* conteurs pour parcourire les images */
  pixel pixFinal, pixTmp;

  /* On vérifie que les tableaux de pixel données en paramètres sont existants */ 
  assert(imageVue != NULL);
  assert(imageCache != NULL);
  assert(imageFinal != NULL);
  
  for (ligne=0; ligne<hauteur; ligne++)
    {
      for (colonne=0; colonne<largeur; colonne++)
	{
	  pixFinal = imageVue[colonne + ligne * largeur];
	  pixTmp = imageCache[colonne + ligne * largeur];

	  for(posO = 5, posD = 0; posO <=  7 ; posO++, posD++)
	    {
	      changePixIemeBit(pixTmp.red, &pixFinal.red, posO, posD);
	      changePixIemeBit(pixTmp.green, &pixFinal.green, posO, posD);
	      changePixIemeBit(pixTmp.blue, &pixFinal.blue, posO, posD);
	    }
	  imageFinal[colonne + ligne * largeur] = pixFinal;
	}
    } 
}

/* Preconditions : imCache est un tableau 1D de pixels, suffisamment grand pour contenir 
   tous les pixels de l'image en mode donnée-resultat. imEntrelace est un tableau de pixels de l'image visible,
   qui contient deux image entrelacées. Largeur et hauteur contiennent la largeur (nombre de colonnes) et 
   la hauteur (nombre de lignes) de l'image, en nombre de pixels (mode donées).
 
   Postconditions : les pixels de imCache contiennent pour chaque valeurs (R,G,B) les 3 bits
   de poid faible de imEntrelace à la place des 3 bites de poid fort puis les 5 bites suivants
   sont à 0.
*/
void extraction (const pixel * imEntrelace, pixel *imCache, const unsigned int hauteur, const unsigned int largeur)
{
  unsigned char posO; 
  unsigned char posD ; 
  int ligne, colonne ; 

  for (ligne = 0; ligne < hauteur; ligne++)
    {
      for (colonne = 0; colonne < largeur; colonne++)
	{
	  imCache[colonne + ligne * largeur].red = 0 ;
	  imCache[colonne + ligne * largeur].green = 0 ;
	  imCache[colonne + ligne * largeur].blue = 0 ;
	  
	  for (posO = 0, posD = 5; posD <= 7 ; posO++, posD++)
	    {
	      changePixIemeBit(imEntrelace[colonne + ligne * largeur].red, &imCache[colonne + ligne * largeur].red, posO, posD);
	      changePixIemeBit(imEntrelace[colonne + ligne * largeur].green, &imCache[colonne + ligne * largeur].green, posO, posD);
	      changePixIemeBit(imEntrelace[colonne + ligne * largeur].blue, &imCache[colonne + ligne * largeur].blue, posO, posD);
	   }
	}
    }
}

/* -------------------------- */
/*     Completez le main      */
/* -------------------------- */


int main()
{ 
  char nomFicImage1[] = "gargouille.bmp" ;
  char nomFicImage2[] = "F15.bmp" ;
  unsigned int largeur, hauteur, p = 5 ;
  pixel * imageLue1 ;
  pixel * imageLue2 ;
  pixel * imageS ;
  pixel * imageD ;
  pixel * imageE ;

  /* Conversion des images .bmp originaux en tableau de pixel */
  lireEntete(nomFicImage1,&largeur,&hauteur) ;
  imageLue1 = (pixel*) malloc(sizeof(pixel)*largeur*hauteur) ;
  remplirTableauPixelsDepuisFichier(nomFicImage1,imageLue1,largeur,hauteur) ;
  lireEntete(nomFicImage2,&largeur,&hauteur) ;
  imageLue2 = (pixel*) malloc(sizeof(pixel)*largeur*hauteur) ;
  remplirTableauPixelsDepuisFichier(nomFicImage2,imageLue2,largeur,hauteur) ;
  
  /* copie du fichier nomFicImage1 dans test.bmp par l'intermédiaire
     du tableau de pixel imageLue1 et de même pour nomFicImage2.
  */
  ecrireFichier("testImage1.bmp",imageLue1,largeur,hauteur) ;
  ecrireFichier("testImage2.bmp",imageLue2,largeur,hauteur) ;

  /* Instruction qui affiche les trois niveaux R,G,B qui se trouve à la 7eme ligne et 10eme colonne */
  printf("red : %u\ngreen : %u\nblue : %u\n",imageLue1 [6*largeur+9].red,imageLue1 [6*largeur+9].red,imageLue1 [6*largeur+9].red) ;
  
  /* Utilisation de la procédure traitementSepia et création du fichier sepia.bmp */
  imageS = (pixel*) malloc(sizeof(pixel)*largeur*hauteur) ;
  traitementSepia(imageLue1, imageS , largeur, hauteur, p) ;
  ecrireFichier("sepia.bmp",imageS,largeur,hauteur) ;
  
  /* Utilisation de la procédure dissimulation et création du fichier dissimulation.bmp */
  imageD = (pixel*) malloc(sizeof(pixel)*largeur*hauteur) ;
  dissimulation(imageLue1, imageLue2, imageD, largeur, hauteur) ;
  ecrireFichier("dissimulation.bmp",imageD,largeur,hauteur) ; 
  
  /* Utilisation de la procédure extraction puis création de l'image extraction.bmp */
  imageE = (pixel*) malloc(sizeof(pixel)*largeur*hauteur) ;
  extraction(imageD, imageE, largeur, hauteur) ;
  ecrireFichier("extraxtion.bmp", imageE,largeur, hauteur) ;
  
  /* Liberation de la mémoire alloué aux tableaux de pixel */
  free(imageLue1) ;
  free(imageLue2) ;
  free(imageS) ;
  free(imageD) ;
  free(imageE) ;
  
  return 0;
}


/* Procedure auxiliaire utile pour lire les fichiers */
void fskip(FILE *fp, int num_bytes)
{
  int i;
  for (i=0; i<num_bytes; i++) {fgetc(fp);}
}




void lireEntete(const char nomFichier[], unsigned int * largeur, unsigned int * hauteur)
{
  
  FILE * fic = fopen(nomFichier, "rb");
  unsigned short bitsparpixel = 0;
  unsigned int compression = 0, nbcolorsinpalette = 0, nbimportantcolors = 0;

  if (fgetc(fic)!='B' || fgetc(fic)!='M')
  {
    fclose(fic);
    fprintf(stderr, "%s n'est pas au format BMP.\n",nomFichier);
    exit(EXIT_FAILURE);
  }

  fskip(fic,16);
  fread(largeur, 4, 1, fic);
  fread(hauteur, 4, 1, fic);

  fskip(fic, 2); /* skipping the number of color planes (always 1) */
  fread(&bitsparpixel, 2, 1, fic);

  if(bitsparpixel != 24) 
    {
      fclose(fic);      
      fprintf(stderr, "Erreur: le nombre de bits par pixel n'est pas 24, \n");
      fprintf(stderr, "ce format d'image n'est pas supporte par ce programme. \n");
      exit(EXIT_FAILURE);
    }
  fread(&compression, 4, 1, fic);
  if(compression != 0) 
    {
      fclose(fic);
      fprintf(stderr, "Erreur: le mode de compression n'est pas 0, \n");
      fprintf(stderr, "ce format d'image n'est pas supporte par ce programme. \n");
      exit(EXIT_FAILURE);
    }

  fskip(fic,12);
  fread(&nbcolorsinpalette, 4, 1, fic);
  if(nbcolorsinpalette != 0) 
    {
      fclose(fic);
      fprintf(stderr, "Erreur: le nombre de couleurs dans la palette n'est pas 0, \n");
      fprintf(stderr, "ce format d'image n'est pas supporte par ce programme. \n");
      exit(EXIT_FAILURE);
    }

  fread(&nbimportantcolors, 4, 1, fic);
  if(nbimportantcolors != 0) 
    {
      fclose(fic);
      fprintf(stderr, "Erreur: le nombre de couleurs importantes n'est pas 0, \n");
      fprintf(stderr, "ce format d'image n'est pas supporte par ce programme. \n");
      exit(EXIT_FAILURE);
    }


  fclose(fic);
}





void remplirTableauPixelsDepuisFichier(const char nomFichier[], pixel tab[], \
                                       unsigned int largeur, unsigned int hauteur)
{
  FILE * fic = fopen(nomFichier, "rb");
  unsigned int ligne, colonne, padding;

  fskip(fic, 54);
  for(ligne = 0; ligne < hauteur; ligne ++)
    {
      for (colonne = 0; colonne < largeur; colonne ++)
        {
          tab[(ligne*largeur) + colonne].red = fgetc(fic);
          tab[(ligne*largeur) + colonne].green = fgetc(fic);
          tab[(ligne*largeur) + colonne].blue = fgetc(fic);
        }

      /* Padding for 4 byte alignment */
      if( (3*largeur)%4 == 0) padding = 0;
      else padding = 4 - ((3*largeur)%4);
      fskip(fic, padding);
    }

  fclose(fic);
}



void ecrireFichier(const char nomFic[], const pixel tab[], \
                   unsigned int largeur, unsigned int hauteur)
{
  FILE * fic = fopen(nomFic, "wb");
  unsigned int monInt;
  unsigned short monShort;
  unsigned char monChar, i;
  unsigned int padding, ligne, colonne, index;

  char format[] = {'B', 'M'};
  unsigned int tailleFic = 54 + largeur*hauteur*3;

  
  fwrite(format, 1, 2, fic);

  /* size of the file in bytes */
  fwrite(&tailleFic, 4, 1, fic);

  /* Unused, app specific */
  monShort = 0;
  fwrite(&monShort, 2, 1, fic);
  fwrite(&monShort, 2, 1, fic);

  /* Offset for pixel data */
  monInt = 54;
  fwrite(&monInt, 4, 1, fic);

  /* Nb of bytes in the header from this point */
  monInt = 40;
  fwrite(&monInt, 4, 1, fic);

  fwrite(&largeur, 4, 1, fic);
  fwrite(&hauteur, 4, 1, fic);

  /* Nb of color planes (always 1) */
  monShort = 1;
  fwrite(&monShort, 2, 1, fic);

  /* Nb of bits per pixel */
  monShort = 24;
  fwrite(&monShort, 2, 1, fic);
  
  /* Compression mode */
  monInt = 0;
  fwrite(&monInt, 4, 1, fic);


  /* Size of the raw BMP data (after this header), including padding */
  if( (3*largeur)%4 == 0) padding = 0;
  else padding = 4 - ((3*largeur)%4);
  monInt = (largeur*3 + padding)*hauteur;
  fwrite(&monInt, 4, 1, fic);


  /* Horiz and vertic resolutions (pixel per metre) */
  monInt = 2835;
  fwrite(&monInt, 4, 1, fic);
  fwrite(&monInt, 4, 1, fic);


  /* Numbers of colors in the palette & number of important colors */ 
  monInt = 0;
  fwrite(&monInt, 4, 1, fic);
  fwrite(&monInt, 4, 1, fic);
 

  /* Pixel data */
  
  for(ligne = 0; ligne < hauteur; ligne ++)
    {
      for (colonne = 0; colonne < largeur; colonne ++)
        {
          index = largeur*ligne + colonne;
          fwrite(&tab[index].red, 1, 1, fic);
          fwrite(&tab[index].green, 1, 1, fic);
          fwrite(&tab[index].blue, 1, 1, fic);
        }

      /* Padding for 4 byte alignment */
      if( (3*largeur)%4 == 0) padding = 0;
      else padding = 4 - ((3*largeur)%4);
      monChar = 0;
      for (i = 0; i < padding; i++) fwrite(&monChar, 1, 1, fic);
    }

  fclose(fic);
}




unsigned char getIemeBit(unsigned char nombre, unsigned char numbit)
{
  return (nombre & (1 << numbit)) >> numbit;
}




void setIemeBit0(unsigned char * nombre, unsigned char numbit)
{
  *nombre = (*nombre) & (~(1 << numbit));
}



void setIemeBit1(unsigned char * nombre, unsigned char numbit)
{
  *nombre = (*nombre) | (1 << numbit);
}
